package com.hil.eventbus.compile;

import com.google.auto.service.AutoService;
import com.hil.eventbus.Subscriber;
import com.hil.eventbus.Subscription;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

@SuppressWarnings("unchecked")
@AutoService(Processor.class)
public class EasyEventBusProcessor extends AbstractProcessor {


   public static final String PACKAGE_NAME = "com.hil.eventbus";
   public static final String CLASS_NAME = "EventBusInject";
    public static final String DOC = "这是自动生成的代码 by hil";

    LinkedHashMap methodsByClass = new LinkedHashMap<TypeElement,List<ExecutableElement>>();

    Elements mElementUtils;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mElementUtils =  processingEnv.getElementUtils();
        System.out.println("EasyEventBusProcessor init ");
    }


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>();
        set.add(Subscriber.class.getCanonicalName());
        return set;
    }


    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("EasyEventBusProcessor process ");

        Messager messager =  processingEnv.getMessager();
        collectSubscribers(roundEnv, messager);
        if(methodsByClass.isEmpty() ){
            messager.printMessage(Diagnostic.Kind.WARNING, "No @Event annotations found");
        } else {
          TypeSpec.Builder  typeBuilder = TypeSpec.classBuilder(CLASS_NAME)
                  .addModifiers(Modifier.PUBLIC)
                  .addJavadoc(DOC)
                  .addField(generateSubscriberField())
                  .addMethod(generateMethodPutIndex())
                  .addMethod(generateMethodGetSubscriberInfo());
            generateInitializerBlock(typeBuilder);
            JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, typeBuilder.build())
                    .build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (Throwable e) {
            }
        }
        return true;
    }


    private void collectSubscribers(RoundEnvironment roundEnvironment,Messager messager ){
        Set<? extends Element> elementSet =   roundEnvironment.getElementsAnnotatedWith(Subscriber.class);
        if (elementSet ==null || elementSet.isEmpty()){
            return;
        }


        for (Element element : elementSet){
            if (element instanceof ExecutableElement){
                ExecutableElement executableElement = (ExecutableElement) element;
                    if (checkHasNoErrors(executableElement, messager)){
                        TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
                        List<ExecutableElement>   list = (List<ExecutableElement>) methodsByClass.get(typeElement);
                        if (null == list){
                            list = new LinkedList<>();
                            methodsByClass.put(typeElement, list);
                        }
                        list.add(executableElement);
                    }
            }else {
                //@Event 只能用于修改方法
                messager.printMessage(
                        Diagnostic.Kind.ERROR,
                        "@Event is only valid for methods",
                        element
                );
            }
        }
    }

    private boolean checkHasNoErrors(ExecutableElement element, Messager messager){
       if (element.getModifiers().contains(Modifier.STATIC)){
           messager.printMessage(Diagnostic.Kind.ERROR, "Event method must not be static", element);
           return false;
       }
        //必须是 public 方法
        if (!element.getModifiers().contains(Modifier.PUBLIC)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "Event method must be public", element);
            return false;
        }
        //方法只能且最多包含一个参数
        List<? extends VariableElement> parameters = element.getParameters();
        if (parameters.size() != 1){
            messager.printMessage(
                    Diagnostic.Kind.ERROR,
                    "Event method must have exactly 1 parameter",
                    element);
            return false;
        }
        return true;

    }


    //生成 subscriberIndex 这个静态常量
    private FieldSpec generateSubscriberField()  {
        ParameterizedTypeName subscriberIndex = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                getClassAny(),
                ClassName.get(Subscription.class)
        );
        return FieldSpec.builder(subscriberIndex, "subscriberIndex")
                .addModifiers(
                        Modifier.PRIVATE,
                        Modifier.STATIC,
                        Modifier.FINAL
                )
                .initializer(
                        "new ${'$'}T<Class<?>, ${'$'}T>()",
                        HashMap.class,
                        Subscription.class
            )
            .build();
    }
    private TypeName getClassAny()  {
        return ParameterizedTypeName.get(
                ClassName.get(Class.class),
             WildcardTypeName.subtypeOf(Object.class)
        );
    }
    //生成静态方法块
    private void  generateInitializerBlock(TypeSpec.Builder builder) {
      Iterator<TypeElement> typeElementIterator = methodsByClass.entrySet().iterator();
        while (typeElementIterator.hasNext()) {
            TypeElement typeElement = typeElementIterator.next();
            List<ExecutableElement> methods = (List<ExecutableElement>)methodsByClass.get(typeElement);
            if (methods.isEmpty()) {
                break;
            }
            CodeBlock.Builder codeBuilder = CodeBlock.builder();
            codeBuilder.add(
                    "${'$'}T<${'$'}T> eventMethodInfoList = new ${'$'}T<${'$'}T>();",
                    List.class,
                    Subscription.class,
                    ArrayList.class,
                    Subscription.class
            );
            for (ExecutableElement executableElement : methods){

                String  methodName = executableElement.getSimpleName().toString();
                TypeMirror eventType = executableElement.getParameters().get(0).asType();
                codeBuilder.add(
                        "eventMethodInfoList.add(new Subscription(${'$'}S, ${'$'}T.class));",
                        methodName,
                        eventType
                );
            }

            codeBuilder.add(
                    "Subscription subscriberInfo = new Subscription(${'$'}T.class, eventMethodInfoList); putIndex(subscriberInfo);",
                    typeElement.asType()
            );
            builder.addInitializerBlock(
                    codeBuilder.build()
            );
        }
    }

    //生成 putIndex 方法
    private MethodSpec generateMethodPutIndex(){
        return MethodSpec.methodBuilder("putIndex")
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                .returns(Void.TYPE)
                .addParameter(Subscription.class, "info")
            .addCode(
                CodeBlock.builder().add("subscriberIndex.put(info.getSubscriberClass() , info);")
                        .build()
        )
                .build();
    }

    //生成 getSubscriberInfo 方法
    private MethodSpec generateMethodGetSubscriberInfo()   {
        return MethodSpec.methodBuilder("getSubscriberInfo")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .returns(Subscription.class)
            .addParameter(getClassAny(), "subscriberClass")
                .addCode(
                        CodeBlock.builder().add("return subscriberIndex.get(subscriberClass);")
                                .build()
                )
                .build();
    }
}